package primitivematchmaker;

import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.mindswap.owl.OWLClass;
import org.mindswap.owl.OWLDataValue;
import org.mindswap.owl.OWLFactory;
import org.mindswap.owl.OWLIndividual;
import org.mindswap.owl.OWLIndividualList;
import org.mindswap.owl.OWLKnowledgeBase;
import org.mindswap.owl.OWLProperty;
import org.mindswap.owl.OWLValue;
import org.mindswap.owls.grounding.Grounding;
import org.mindswap.owls.process.Process;
import org.mindswap.owls.process.variable.Input;
import org.mindswap.owls.process.variable.Output;
import org.mindswap.owls.process.variable.Parameter;
import org.mindswap.owls.profile.Profile;
import org.mindswap.owls.service.Service;

import primitivematchmaker.data.ConceptsRelation;
import primitivematchmaker.data.MatchedService;
import primitivematchmaker.data.RelationDescription;
import primitivematchmaker.exception.LackOfOntologyException;
import primitivematchmaker.exception.UnrelatedClassesMatchedException;
import primitivematchmaker.util.PermutationGenerator;

/**
 * PrimitiveMatchmaker is a simple semantic OWL-S service descriptions
 * comparison and matching system. It's concept is rather more primitive than
 * simple - it only matches services with equal length arguments lists and
 * selects only those, which are matched as either exact or subsumption
 * relation. Services matched as plugin are treated as no match at all.
 * 
 * @author tptak
 * 
 */
public class PrimitiveMatchmaker {
	/**
	 * Knowledgebase used to gather ontologies and services to match the
	 * requirement against.
	 */
	private OWLKnowledgeBase owlKnowledgeBase;
	/**
	 * Temporary knowledge base used, so that the requirement description isn't
	 * added to the main knowledge base.
	 */
	private OWLKnowledgeBase tmpKnowledgeBase;
	/**
	 * Parameter to determine whether ontologies not yet in the knowledgebase
	 * should be read and added or not.
	 */
	private boolean addUnknownOntologiesOnDemand;
	/**
	 * Parameter used to calculate similarity between to OWL classes. Let's say
	 * it is x (for shorter name). Then the similarity is calculated as x/x+d,
	 * where d is distance between the two classes in inheritance hierarchy.
	 */
	private double similarityMeasureParameter;
	/**
	 * Parameter to prevent very long (or indefinite) searching for distance
	 * between two classes. This should end at some time if we already know the
	 * relation, but in case it get's too time consuming, it will be stopped
	 * when the distance is big enough to make the similarity measure small
	 * enough to worsen the overall result.
	 */
	private int maximum_distance_allowed;
	static Logger logger = Logger.getRootLogger();

	/**
	 * Default constuctor, sets some default values to all configuration
	 * parameters. It also sets up the knowledge bases
	 */
	public PrimitiveMatchmaker() {
		super();

		owlKnowledgeBase = OWLFactory.createKB();
		owlKnowledgeBase.setReasoner("Pellet");
		addUnknownOntologiesOnDemand = true;
		similarityMeasureParameter = 5;
		maximum_distance_allowed = 10;
	}

	/**
	 * Adds ontology to the knowledge base, using ontology in string
	 * 
	 * @param ontology
	 *            the ontology to be added
	 * @throws Exception
	 */
	public void addOntologyFromString(String ontology) throws Exception {
		owlKnowledgeBase.read(new StringReader(ontology), null);
		owlKnowledgeBase.refresh();

	}

	/**
	 * Adds ontology to the knowledge base, using ontology in url
	 * 
	 * @param url
	 *            URL of the ontology to be added
	 * @throws Exception
	 */
	public void addOntologyFromUrl(String url) throws Exception {
		owlKnowledgeBase.read(new URI(url));
		owlKnowledgeBase.refresh();

	}

	/**
	 * Adds service with given URI and placed in given string
	 * 
	 * @param service
	 *            The service string
	 * @param baseUri
	 *            The URI of service to be added.
	 * @throws Exception
	 */
	public void addServiceFromString(String service, String baseUri)
			throws Exception {
		owlKnowledgeBase.readAllServices(new StringReader(service), new URI(
				baseUri));
		owlKnowledgeBase.refresh();

	}

	/**
	 * Adds service with given URI. It looks for it on the network.
	 * 
	 * @param baseUri
	 *            The URI of service to be added.
	 * @throws Exception
	 */
	public void addServiceFromUrl(String url) throws Exception {
		owlKnowledgeBase.readService(new URI(url));
		owlKnowledgeBase.refresh();
	}

	/**
	 * Matches the services stored in the knowledgebase against the requirement
	 * given in string
	 * 
	 * @param service
	 *            string with service requirement given
	 * @return list with description of matched services
	 * @throws Exception
	 */
	public List<MatchedService> matchServiceByString(String service)
			throws Exception {
		logger.debug("matchServiceByString(String) - start"); //$NON-NLS-1$

		OWLIndividualList<Service> services = owlKnowledgeBase
				.getServices(false);
		tmpKnowledgeBase = OWLFactory.createKB();
		tmpKnowledgeBase.setReasoner("Pellet");
		Service requirement = tmpKnowledgeBase.readService(new StringReader(
				service), null);
		Map<Service, RelationDescription> results = new HashMap<Service, RelationDescription>();
		for (Service candidate : services) {
			results.put(candidate, compareServices(requirement, candidate));
		}

		List<MatchedService> result = new LinkedList<MatchedService>();

		for (Service serviceKey : results.keySet()) {
			if (results.get(serviceKey).getConceptsRelation() == ConceptsRelation.NONE) {
				continue;
			}
			MatchedService tmp = new MatchedService();
			tmp.setServiceUri(serviceKey.getURI());
			tmp.setConceptsRelation(results.get(serviceKey)
					.getConceptsRelation());
			tmp.setSimilarityMeasure(results.get(serviceKey)
					.getSimilarityScore());
			int i = 0;
			while (i < result.size()
					&& result.get(i).getSimilarityMeasure() > tmp
							.getSimilarityMeasure()) {
				i++;
			}
			result.add(i, tmp);
		}

		logger.debug("matchServiceByString(String) - end"); //$NON-NLS-1$

		return result;
	}

	/**
	 * Matches the services stored in the knowledgebase against the requirement
	 * given by URL.
	 * 
	 * @param url
	 *            URL where the requirement should be looked for
	 * @return list with description of matched services
	 * @throws Exception
	 */
	public List<MatchedService> matchServiceByUrl(String url) throws Exception {
		logger.debug("matchServiceByUrl(String) - start"); //$NON-NLS-1$

		OWLIndividualList<Service> services = owlKnowledgeBase
				.getServices(false);
		tmpKnowledgeBase = OWLFactory.createKB();
		tmpKnowledgeBase.setReasoner("Pellet");
		Service requirement = tmpKnowledgeBase.readService(new URI(url));
		Map<Service, RelationDescription> results = new HashMap<Service, RelationDescription>();
		for (Service candidate : services) {
			results.put(candidate, compareServices(requirement, candidate));
		}

		List<MatchedService> result = new LinkedList<MatchedService>();

		for (Service serviceKey : results.keySet()) {
			if (results.get(serviceKey).getConceptsRelation() == ConceptsRelation.NONE) {
				continue;
			}
			MatchedService tmp = new MatchedService();
			tmp.setServiceUri(serviceKey.getURI());
			tmp.setConceptsRelation(results.get(serviceKey)
					.getConceptsRelation());
			tmp.setSimilarityMeasure(results.get(serviceKey)
					.getSimilarityScore());
			int i = 0;
			while (i < result.size()
					&& result.get(i).getSimilarityMeasure() > tmp
							.getSimilarityMeasure()) {
				i++;
			}
			result.add(i, tmp);
		}

		logger.debug("matchServiceByUrl(String) - end"); //$NON-NLS-1$

		return result;
	}

	/**
	 * Finds ontology in the knowledgebase and removes it if it is found
	 * 
	 * @param uri
	 *            The uri of ontology to be removed
	 * @throws Exception
	 */
	public void removeOntology(String uri) throws Exception {
		owlKnowledgeBase.unload(new URI(uri));
		owlKnowledgeBase.refresh();

	}

	/**
	 * Checks whether given service is available in the knowledge base.
	 * 
	 * @param uri
	 *            URI of the service to look for
	 * @return true if the service is available, false otherwise
	 * @throws Exception
	 */
	public boolean isServiceAvailable(String uri) throws Exception {
		OWLIndividualList<Service> services = owlKnowledgeBase
				.getServices(false);
		URI uriuri = new URI(uri);
		for (Service service : services) {
			if (service.getURI().equals(uriuri)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Removes service from the knowledgebase if it is there Note: This method
	 * leaves garbage, but the main target is achieved
	 * 
	 * @param uri
	 *            URI of the service to be returned
	 * @throws Exception
	 */
	public void removeService(String uri) throws Exception {
		Service service = owlKnowledgeBase.getService(new URI(uri));

		// remove properties of profiles
		OWLIndividualList<Profile> profiles = service.getProfiles();
		for (Profile profile : profiles) {
			removeProperties(profile, profile.getProperties());
		}

		// remove properties of process
		Process process = service.getProcess();
		removeProperties(process, process.getProperties());

		// remove properties of groundings
		OWLIndividualList<Grounding> groundings = service.getGroundings();
		for (Grounding grounding : groundings) {
			removeProperties(grounding, grounding.getProperties());
		}

		// remove properties of service
		removeProperties(service, service.getProperties());
		owlKnowledgeBase.remove(service, false);
		owlKnowledgeBase.refresh();

	}

	/**
	 * Property removal method used to clear as much as possible from the
	 * knowledge base, when a service is to be removed.
	 * 
	 * @param ind
	 *            Individual, to which properties correspond
	 * @param map
	 *            Map of properties and their values, which make triples with
	 *            the individual. All those triples will be removed
	 */
	private void removeProperties(OWLIndividual ind,
			Map<OWLProperty, List<OWLValue>> map) {
		for (Map.Entry<OWLProperty, List<OWLValue>> tmp : map.entrySet()) {
			for (OWLValue val : tmp.getValue()) {
				owlKnowledgeBase.removeProperty(ind, tmp.getKey(), val);
			}
		}
	}

	/**
	 * Main method in this code. It's purpose is to compare two service
	 * descriptions.
	 * 
	 * @param requirement
	 *            Requirement service description
	 * @param candidate
	 *            Candidate service description
	 * @return Description of relation between two services
	 * @throws Exception
	 */
	public RelationDescription compareServices(Service requirement,
			Service candidate) throws Exception {
		logger.debug("compareServices(Service, Service) - start");
		RelationDescription result = new RelationDescription();
		RelationDescription bestResult = new RelationDescription();
		bestResult.setConceptsRelation(ConceptsRelation.NONE);
		bestResult.setSimilarityScore(0);
		Profile requirementProfile = requirement.getProfile();
		OWLIndividualList<Profile> candidateProfiles = candidate.getProfiles();
		for (Profile candidateProfile : candidateProfiles) {
			OWLIndividualList<Input> requirementInputs = requirementProfile
					.getInputs();
			OWLIndividualList<Input> candidateInputs = candidateProfile
					.getInputs();
			OWLIndividualList<Output> requirementOutputs = requirementProfile
					.getOutputs();
			OWLIndividualList<Output> candidateOutputs = candidateProfile
					.getOutputs();

			logger
					.debug("compareServices(Service, Service) - data fetching complete");

			if (requirementInputs.size() != candidateInputs.size()
					|| requirementOutputs.size() != candidateOutputs.size()) {

				logger
						.debug("compareServices(Service, Service) - end for profile - unequal sizes of inputs or outputs"); //$NON-NLS-1$
				continue;

			}

			if (requirementInputs.size() == 0 && requirementOutputs.size() == 0
					&& candidateInputs.size() == 0
					&& candidateOutputs.size() == 0) {
				result.setConceptsRelation(ConceptsRelation.EXACT);
				result.setSimilarityScore(1.0);

				logger
						.debug("compareServices(Service, Service) - end - no inputs or outputs"); //$NON-NLS-1$
				return result;
			}

			logger.debug("compareServices(Service, Service) - compare inputs");

			Map<Parameter, Map<Parameter, RelationDescription>> requirementCandidateInputsRelation = confrontParameterLists(
					requirementInputs, candidateInputs);

			logger.debug("compareServices(Service, Service) - compare outputs");

			Map<Parameter, Map<Parameter, RelationDescription>> requirementCandidateOutputsRelation = confrontParameterLists(
					requirementOutputs, candidateOutputs, true);

			logger
					.debug("compareServices(Service, Service) - find best inputs-outputs combination");

			RelationDescription bestInputsCombination = getBestParametersCombination(requirementCandidateInputsRelation);
			RelationDescription bestOutputsCombination = getBestParametersCombination(requirementCandidateOutputsRelation);

			logger.debug("compareServices(Service, Service) - prepare results");

			ConceptsRelation inRel = bestInputsCombination
					.getConceptsRelation();
			ConceptsRelation outRel = bestOutputsCombination
					.getConceptsRelation();
			if (inRel.equals(ConceptsRelation.NONE)
					|| outRel.equals(ConceptsRelation.NONE)) {
				result.setConceptsRelation(ConceptsRelation.NONE);
				result.setSimilarityScore(0.0);
			} else if (inRel.equals(ConceptsRelation.EXACT)
					&& outRel.equals(ConceptsRelation.EXACT)) {
				result.setConceptsRelation(ConceptsRelation.EXACT);
				result.setSimilarityScore(1.0);
			} else {
				result.setConceptsRelation(ConceptsRelation.SUBSUME);
				result.setSimilarityScore((bestInputsCombination
						.getSimilarityScore() + bestOutputsCombination
						.getSimilarityScore()) / 2.0);
			}
			if (result.getSimilarityScore() > bestResult.getSimilarityScore()) {
				if (result.getSimilarityScore() == 1.0) {
					return result;
				}
				bestResult.setSimilarityScore(result.getSimilarityScore());
				bestResult.setConceptsRelation(result.getConceptsRelation());
			}
		}

		logger.debug("compareServices(Service, Service) - end"); //$NON-NLS-1$

		return bestResult;
	}

	/**
	 * Finds the best match between parameters of requirement and candidate
	 * service descriptions
	 * 
	 * @param requirementCandidateParametersRelation
	 *            the map of relations between parameters
	 * @return The relation description which gives the best result
	 */
	private RelationDescription getBestParametersCombination(
			Map<Parameter, Map<Parameter, RelationDescription>> requirementCandidateParametersRelation) {

		logger
				.debug("getBestParametersCombination(Map<Parameter,Map<Parameter,RelationDescription>>) - start");

		RelationDescription bestResult = new RelationDescription();
		bestResult.setConceptsRelation(ConceptsRelation.NONE);
		bestResult.setSimilarityScore(0.0);

		if (requirementCandidateParametersRelation.size() == 0) {
			logger
					.debug("getBestParametersCombination(Map<Parameter,Map<Parameter,RelationDescription>>) - end"); //$NON-NLS-1$
			bestResult.setConceptsRelation(ConceptsRelation.EXACT);
			bestResult.setSimilarityScore(1.0);
			return bestResult;
		}

		Parameter[] requirementParameters = requirementCandidateParametersRelation
				.keySet().toArray(new Parameter[0]);
		Parameter[] candidateParameters = requirementCandidateParametersRelation
				.get(requirementParameters[0]).keySet().toArray(
						new Parameter[0]);

		int size = requirementParameters.length;

		PermutationGenerator reqPermutationsIterator = new PermutationGenerator(
				size);
		PermutationGenerator candPermutationsIterator = new PermutationGenerator(
				size);

		logger
				.debug("getBestParametersCombination(Map<Parameter,Map<Parameter,RelationDescription>>) - perform permutations comparison");

		for (int[] reqPermutation : reqPermutationsIterator) {
			candPermutationsIterator.reset();
			for (int[] candPermutation : candPermutationsIterator) {
				double tmpMeasureSum = 0;

				RelationDescription candidateDescription = new RelationDescription();
				candidateDescription.setConceptsRelation(ConceptsRelation.NONE);

				for (int i = 0; i < size; i++) {

					RelationDescription tmpDescription = requirementCandidateParametersRelation
							.get(requirementParameters[reqPermutation[i]]).get(
									candidateParameters[candPermutation[i]]);

					// Jesli choc jedna relacja jest tego typu, to calosc bedzie
					// do niczego
					if (tmpDescription.getConceptsRelation().equals(
							ConceptsRelation.NONE)
							|| tmpDescription.getConceptsRelation().equals(
									ConceptsRelation.PLUGIN)) {
						// brak polaczenia mnie interesujacego
						break;
					}

					tmpMeasureSum += tmpDescription.getSimilarityScore();

					if (candidateDescription.getConceptsRelation().equals(
							ConceptsRelation.EXACT)) {
						if (tmpDescription.getConceptsRelation().equals(
								ConceptsRelation.SUBSUME)) {
							candidateDescription
									.setConceptsRelation(ConceptsRelation.SUBSUME);
						}

					} else if (candidateDescription.getConceptsRelation()
							.equals(ConceptsRelation.NONE)) {
						candidateDescription.setConceptsRelation(tmpDescription
								.getConceptsRelation());
					}
				}

				// Czy jest co sprawdzac?
				if (!candidateDescription.getConceptsRelation().equals(
						ConceptsRelation.NONE)) {

					// Jest - ustaw Similarity Score
					candidateDescription.setSimilarityScore(tmpMeasureSum
							/ size);

					// Czy mamy idealne dopasowanie?
					if (candidateDescription.getConceptsRelation().equals(
							ConceptsRelation.EXACT)) {
						logger
								.debug("getBestParametersCombination(Map<Parameter,Map<Parameter,RelationDescription>>) - end"); //$NON-NLS-1$
						return candidateDescription;
					}

					// Skoro nie mamy, czy mamy lepiej niz obecnie w bestResult?
					if (bestResult.getConceptsRelation().equals(
							ConceptsRelation.NONE)) {
						bestResult = candidateDescription;
						// Jesli nie mamy lepiej, to mamy identycznie (bo nie
						// NONE i nie EXACT, PLUGIN odpada wiec zostanie
						// SUBSUME). Sprawdzmy wiec czy miara podobienstwa sie
						// poprawila
					} else if (bestResult.getSimilarityScore() < candidateDescription
							.getSimilarityScore()) {
						bestResult.setSimilarityScore(candidateDescription
								.getSimilarityScore());
					}
				}
			}
		}

		logger
				.debug("getBestParametersCombination(Map<Parameter,Map<Parameter,RelationDescription>>) - end"); //$NON-NLS-1$
		return bestResult;
	}

	/**
	 * Takes two lists of parameters and compares all parameters from the first
	 * list with parameters from second list.
	 * 
	 * @param requirementParameters
	 *            List of requirement description parameters
	 * @param candidateParameters
	 *            List of candidate description parameters
	 * @return the map of relations between parameters
	 * @throws Exception
	 */
	private Map<Parameter, Map<Parameter, RelationDescription>> confrontParameterLists(
			OWLIndividualList<? extends Parameter> requirementParameters,
			OWLIndividualList<? extends Parameter> candidateParameters)
			throws Exception {
		return confrontParameterLists(requirementParameters, candidateParameters, false);
	}
		/**
		 * Takes two lists of parameters and compares all parameters from the first
		 * list with parameters from second list.
		 * 
		 * @param requirementParameters
		 *            List of requirement description parameters
		 * @param candidateParameters
		 *            List of candidate description parameters
		 * @return the map of relations between parameters
		 * @throws Exception
		 */
		private Map<Parameter, Map<Parameter, RelationDescription>> confrontParameterLists(
				OWLIndividualList<? extends Parameter> requirementParameters,
				OWLIndividualList<? extends Parameter> candidateParameters, boolean outputs)
				throws Exception {
		logger
				.debug("confrontParameterLists(OWLIndividualList<? extends Parameter>, OWLIndividualList<? extends Parameter>) - start");

		Map<Parameter, Map<Parameter, RelationDescription>> requirementCandidateRelation = new HashMap<Parameter, Map<Parameter, RelationDescription>>();

		for (Parameter requirementParameter : requirementParameters) {
			for (Parameter candidateParameter : candidateParameters) {

				RelationDescription relationDescription = compareParameters(
						requirementParameter, candidateParameter, outputs);

				// ..i zapisywanie do mapy
				Map<Parameter, RelationDescription> candidatesMap;
				if (requirementCandidateRelation
						.containsKey(requirementParameter)) {
					candidatesMap = requirementCandidateRelation
							.get(requirementParameter);
				} else {
					candidatesMap = new HashMap<Parameter, RelationDescription>();
					requirementCandidateRelation.put(requirementParameter,
							candidatesMap);
				}

				candidatesMap.put(candidateParameter, relationDescription);

			}
		}

		logger
				.debug("confrontParameterLists(OWLIndividualList<? extends Parameter>, OWLIndividualList<? extends Parameter>) - end"); //$NON-NLS-1$
		return requirementCandidateRelation;
	}

		/**
		 * Compares two parameters and gives the resulting relation description.
		 * 
		 * @param requirementParameter
		 *            requirement parameter
		 * @param candidateParameter
		 *            candidate parameter
		 * @return the resulting relation description
		 * @throws Exception
		 */
		public RelationDescription compareParameters(
				Parameter requirementParameter, Parameter candidateParameter)
				throws Exception {
			return compareParameters(requirementParameter, candidateParameter, false);
		}
			/**
			 * Compares two parameters and gives the resulting relation description.
			 * 
			 * @param requirementParameter
			 *            requirement parameter
			 * @param candidateParameter
			 *            candidate parameter
			 * @return the resulting relation description
			 * @throws Exception
			 */
			public RelationDescription compareParameters(
					Parameter requirementParameter, Parameter candidateParameter, boolean outputs)
					throws Exception {
		logger.debug("compareParameters(Parameter, Parameter) - start");

		URI perameterTypePropertyURI = new URI(
				"http://www.daml.org/services/owl-s/1.2/Process.owl#parameterType");
		OWLDataValue requirementParameterTypeURI;
		OWLDataValue candidateParameterTypeURI;
		try {
		requirementParameterTypeURI = requirementParameter
				.getPropertiesAsDataValue(perameterTypePropertyURI).get(0);
		candidateParameterTypeURI = candidateParameter
				.getPropertiesAsDataValue(perameterTypePropertyURI).get(0);
		} catch (IndexOutOfBoundsException e) {
			throw new Exception("Either requirement parameter (" + requirementParameter.getURI() + ") or candidate parameter (" + candidateParameter.getURI() + ") is declared to exist but hasn't been declared in document. Verify your input.", e);
		}
		String requirementParameterTypeString = requirementParameterTypeURI
				.getLexicalValue();
		String candidateParameterTypeString = candidateParameterTypeURI
				.getLexicalValue();

		logger
				.debug("compareParameters(Parameter, Parameter) - perform comparison");

		// Tutaj magia z porownywaniem typow...
		RelationDescription returnRelationDescription = compareClasses(new URI(
				requirementParameterTypeString), new URI(
				candidateParameterTypeString), outputs);

		logger.debug("compareParameters(Parameter, Parameter) - end"); //$NON-NLS-1$

		return returnRelationDescription;

	}

	/**
	 * Compares to OWL Classes
	 * 
	 * @param requirement
	 *            requirement class uri
	 * @param candidate
	 *            candidate class uri
	 * @return Relation Description between classes
	 * @throws Exception
	 */
	public RelationDescription compareClasses(URI requirement, URI candidate)
			throws Exception {

		return compareClasses(requirement, candidate, false);
	}

	/**
	 * Compares to OWL Classes
	 * 
	 * @param requirement
	 *            requirement class uri
	 * @param candidate
	 *            candidate class uri
	 * @return Relation Description between classes
	 * @throws Exception
	 */
	public RelationDescription compareClasses(URI requirement, URI candidate,
			boolean outputs) throws Exception {
		logger.debug("compareClasses(URI, URI) - start");
		OWLClass requirementClass = getOWLClass(requirement);
		OWLClass candidateClass = getOWLClass(candidate);
		RelationDescription result = new RelationDescription();

		logger.debug("compareClasses(URI, URI) - perform comparison");

		if (requirementClass.isEquivalentTo(candidateClass)) {

			result.setConceptsRelation(ConceptsRelation.EXACT);
			result.setSimilarityScore(1.0);

		} else if (requirementClass.isSubTypeOf(candidateClass)) {

			result.setConceptsRelation(ConceptsRelation.SUBSUME);
			if (outputs) {
				result.setConceptsRelation(ConceptsRelation.PLUGIN);
			}

			int distance = countDistanceFromUpperToLowerClass(candidateClass,
					requirementClass);

			result.setSimilarityScore(similarityMeasureParameter
					/ (similarityMeasureParameter + distance));

		} else if (candidateClass.isSubTypeOf(requirementClass)) {

			result.setConceptsRelation(ConceptsRelation.PLUGIN);
			if (outputs) {
				result.setConceptsRelation(ConceptsRelation.SUBSUME);
			}

			int distance = countDistanceFromUpperToLowerClass(requirementClass,
					candidateClass);

			result.setSimilarityScore(similarityMeasureParameter
					/ (similarityMeasureParameter + distance));

		} else {

			result.setConceptsRelation(ConceptsRelation.NONE);
			result.setSimilarityScore(0.0);

		}

		logger.debug("compareClasses(URI, URI) - end"); //$NON-NLS-1$

		return result;

	}

	/**
	 * Calculates the distance between two classes in inheritance hierarchy
	 * 
	 * @param upper
	 *            the upper class
	 * @param lower
	 *            the lower class
	 * @return the distance between two classes or maximum_distance_allowed
	 *         parameter, if the distance is grater
	 * @throws Exception
	 */
	private int countDistanceFromUpperToLowerClass(OWLClass upper,
			OWLClass lower) throws Exception {

		logger
				.debug("countDistanceFromUpperToLowerClass(OWLClass, OWLClass) - start");

		if (upper.isSubTypeOf(lower)) {
			OWLClass tmp = lower;
			lower = upper;
			upper = tmp;
		} else if (!lower.isSubTypeOf(upper)) {
			throw new UnrelatedClassesMatchedException("Classes "
					+ upper.toString() + " and " + lower.toString()
					+ " are unrelated.");
		}
		int distance = 0;
		Set<OWLClass> levelSet = upper.getSubClasses(true);
		Set<OWLClass> newLevelSet;

		logger
				.debug("countDistanceFromUpperToLowerClass(OWLClass, OWLClass) - perform measurements");

		while (distance < maximum_distance_allowed) {
			if (levelSet.size() == 0) {
				throw new Exception(
						"No direct connections between two classes found at level "
								+ distance);
			}
			distance++;
			for (OWLClass clazz : levelSet) {
				if (clazz.isEquivalentTo(lower)) {
					return distance;
				}
			}

			newLevelSet = new HashSet<OWLClass>();
			for (OWLClass oWLClass : levelSet) {
				newLevelSet.addAll(oWLClass.getSubClasses(true));
			}
			levelSet = newLevelSet;
		}

		logger
				.debug("countDistanceFromUpperToLowerClass(OWLClass, OWLClass) - end"); //$NON-NLS-1$

		return distance;
	}

	/**
	 * Utility method used to gather classes if they are not in the
	 * knowledgebase and if it is permitted do do so
	 * 
	 * @param uri
	 *            the URI of the class
	 * @return OWL Class description
	 * @throws LackOfOntologyException
	 * @throws IOException
	 */
	private OWLClass getOWLClass(URI uri) throws LackOfOntologyException,
			IOException {
		logger.debug("Running getOWLClass Method");
		OWLClass requiredClass = owlKnowledgeBase.getClass(uri);
		if (requiredClass == null) {
			if (addUnknownOntologiesOnDemand) {

				owlKnowledgeBase.read(uri);
				requiredClass = owlKnowledgeBase.getClass(uri);
			} else {
				throw new LackOfOntologyException(uri.toString()
						+ " could not be found in the knowledge base.");
			}
		}

		if (logger.isDebugEnabled()) {
			logger.debug("getOWLClass(URI) - end"); //$NON-NLS-1$
		}
		return requiredClass;
	}

}
